Chapter 1: Accustoming Yourself to JavaScript
Item 1: Know Which JavaScript You Are Using
Item 2: Understand JavaScript’s Floating-Point Numbers
Item 3: Beware of Implicit Coercions
Item 4: Prefer Primitives to Object Wrappers
1 | var s1 = new String("hello"); |
Since each String object is a separate object, it is only ever equal to itself.
Things to Remember
- Object wrappers for primitive types do not have the same behavior as their primitive values when compared for equality.
- Getting and setting properties on primitives implicitly creates object wrappers.
Item 5: Avoid using ==
with Mixed Types
Things to Remember
- The
==
operator applies a confusing set of implicit coercions when its arguments are of different types. - Use
===
to make it clear to your readers that your comparison does not involve any implicit coercions. - Use your own explicit coercions when comparing values of different types to make your program’s behavior clearer.
Item 6: Learn the Limits of Semicolon Insertion
Things to Remember
- Semicolons are only ever inferred before a }, at the end of a line, or at the end of a program.
- Semicolons are only ever inferred when the next token cannot be parsed.
- Never omit a semicolon before a statement beginning with (, [, +, -, or /.
- When concatenating scripts, insert semicolons explicitly between scripts.
- Never put a newline before the argument to return, throw, break, continue, ++, or –.
- Semicolons are never inferred as separators in the head of a for loop or as empty statements.
Item 7: Think of Strings As Sequences of 16-Bit Code Units
Today there are multiple standard encodings of Unicode, the most popular of which are UTF-8, UTF-16, and UTF-32.
Things to Remember
- JavaScript strings consist of 16-bit code units, not Unicode code points.
- Unicode code points 216 and above are represented in JavaScript by two code units, known as a surrogate pair.
- Surrogate pairs throw off string element counts, affecting length, charAt, charCodeAt, and regular expression patterns such as “.”.
- Use third-party libraries for writing code point-aware string manipulation.
- Whenever you are using a library that works with strings, con-sult the documentation to see how it handles the full range of code points.
Chapter 2: Variable Scope
Item 8: Minimize Use of the Global Object
Since the global object provides a dynamic reflection of the global environment, you can use it to query a running environment to detect which features are available on the platform.
1 | if (!this.JSON) { |
Things to Remember
- Avoid declaring global variables.
- Declare variables as locally as possible.
- Avoid adding properties to the global object.
- Use the global object for platform feature detection.
Item 9: Always Declare Local Variables
Things to Remember
- Always declare new local variables with var.
- Consider using lint tools to help check for unbound variables.
Item 10: Avoid with
Item 11: Get Comfortable with Closures
Things to Remember
- Functions can refer to variables defined in outer scopes.
- Closures can outlive the function that creates them.
- Closures internally store references to their outer variables, and can both read and update their stored variables.
Item 12: Understand Variable Hoisting
A good way to think about the behavior of JavaScript variable decla-rations is to understand them as consisting of two parts: a declara-tion and an assignment.
Things to Remember
- Variable declarations within a block are implicitly hoisted to the top of their enclosing function.
- Redeclarations of a variable are treated as a single variable.
- Consider manually hoisting local variable declarations to avoid confusion.
Item 13: Use Immediately Invoked Function Expressions to Create Local Scopes
Things to Remember
- Understand the difference between binding and assignment.
- Closures capture their outer variables by reference, not by value.
- Use immediately invoked function expressions (IIFEs) to create local scopes.
- Be aware of the cases where wrapping a block in an IIFE can change its behavior.
Item 14: Beware of Unportable Scoping of Named Function Expressions
The real usefulness of named function expressions, though, is for debugging. Most modern JavaScript environments produce stack traces for Error objects, and the name of a function expression is typically used for its entry in a stack trace. Debuggers with facilities for inspecting the stack typically make similar use of named function expressions.
Item 15: Beware of Unportable Scoping of Block-Local Function Declarations
Item 16: Avoid Creating Local Variables with eval
Things to Remember
- Avoid creating variables with eval that pollute the caller’s scope.
- If eval code might create global variables, wrap the call in a nested function to prevent scope pollution.
Item 17: Prefer Indirect eval to Direct eval
Chapter 3: Working with Functions
Item 18: Understand the Difference between Function, Method, and Constructor Calls
Calling a method as a function rarely does anything useful if the method depends on this, since there is no reason to expect the global object to match the expectations that the method has of the object it is called on.
Things to Remember
- Method calls provide the object in which the method property is looked up as their receiver.
- Function calls provide the global object (or undefined for strict functions) as their receiver. Calling methods with function call syntax is rarely useful.
- Constructors are called with new and receive a fresh object as their receiver.
Item 19: Get Comfortable Using Higher-Order Functions
高阶函数的定义
Higher-order functions are nothing more than functions that take other functions as arguments or return functions as their result.
抽象高阶函数的优点
This allows you to fix any bugs in the logic just once, instead of having to hunt for every instance of the coding pattern spread throughout your program. If you find you need to optimize the efficiency of the operation, you again only have one place where you need to change anything. Finally, giving a clear name such as buildString to the abstraction makes it clearer to someone reading the code what the code does, without having to decode the details of the implementation.
Things to Remember
- Higher-order functions are functions that take other functions as arguments or return functions as their result.
- Familiarize yourself with higher-order functions in existing libraries.
- Learn to detect common coding patterns that can be replaced by higher-order functions.
Item 20: Use call to Call Methods with a Custom Receiver
使用 call 方法调用 function
Luckily, functions come with a built-in call method for providing a custom receiver. Invoking a function via its call method:
1 | f.call(obj, arg1, arg2, arg3); |
behaves similarly to calling it directly:、
1 | f(arg1, arg2, arg3); |
except that the first argument provides an explicit receiver object.
Things to Remember
- Use the call method to call a function with a custom receiver.
- Use the call method for calling methods that may not exist on a given object.
- Use the call method for defining higher-order functions that allow clients to provide a receiver for the callback.
Item 21: Use apply to Call Functions with Different Numbers of Arguments
案例
Imagine that someone provides us with a function that calculates the average of any number of values:
1 | average(1, 2, 3); // 2 |
Things to Remember
- Use the
apply
method to call variadic functions with a computed array of arguments. - Use the first argument of
apply
to provide a receiver for variadic methods.
Item 22: Use arguments to Create Variadic Functions
A good rule of thumb is that whenever you provide a variable-arity function for convenience, you should also provide a fixed-arity version that takes an explicit array.
Things to Remember
- Use the implicit arguments object to implement variable-arity functions.
- Consider providing additional fixed-arity versions of the variadic functions you provide so that your consumers don’t need to use the apply method.
Item 23: Never Modify the arguments Object
As a consequence, it is much safer never to modify the arguments object. This is easy enough to avoid by first copying its elements to a real array. A simple idiom for implementing the copy is:
1 | var args = [].slice.call(arguments); |
注意点
The arguments object is not a copy of the function’s arguments
Things to Remember
- Never modify the arguments object.
- Copy the arguments object to a real array using [].slice.call(arguments) before modifying it.
Item 24: Use a Variable to Save a Reference to arguments
案例
Imagine we wish to write a convenience function that takes an arbitrary number of arguments and builds an iterator for those values:
1 | var it = values(1, 4, 1, 4, 2, 1, 3, 5, 6); |
Things to Remember
- Be aware of the function nesting level when referring to arguments.
- Bind an explicitly scoped reference to arguments in order to refer to it from nested functions.
Item 25: Use bind to Extract Methods with a Fixed Receiver
A function’s receiver is determined by how it is called
Things to Remember
- Beware that extracting a method does not bind the method’s receiver to its object.
- When passing an object’s method to a higher-order function, use an anonymous function to call the method on the appropriate receiver.
- Use bind as a shorthand for creating a function bound to the appropriate receiver.
Item 26: Use bind to Curry Functions
Things to Remember
- Use
bind
to curry a function, that is, to create a delegating function with a fixed subset of the required arguments. - Pass
null
orundefined
as the receiver argument to curry a function that ignores its receiver.
Item 27: Prefer Closures to Strings for Encapsulating Code
Things to Remember
- Never include local references in strings when sending them to APIs that execute them with eval.
- Prefer APIs that accept functions to call rather than strings to eval.
Item 28: Avoid Relying on the toString Method of Functions
Things to Remember
- JavaScript engines are not required to produce accurate reflections of function source code via toString.
- Never rely on precise details of function source, since different engines may produce different results from toString.
- The results of toString do not expose the values of local variables stored in a closure.
- In general, avoid using toString on functions.
Item 29: Avoid Nonstandard Stack Inspection Properties
Things to Remember
- Avoid the nonstandard arguments.caller and arguments.callee, because they are not reliably portable.
- Avoid the nonstandard caller property of functions, because it does not reliably contain complete information about the stack.
Chapter 4: Objects and Prototypes
Item 30: Understand the Difference between prototype
, getPrototypeOf
, and __proto__
区别
C.prototype
is used to establish the prototype of objects created bynew C()
.Object.getPrototypeOf(obj)
is the standard ES5 mechanism for retrieving obj’s prototype object.obj.__proto__
is a nonstandard mechanism for retrieving obj’s prototype object.
Things to Remember
- C.prototype determines the prototype of objects created by new C().
- Object.getPrototypeOf(obj) is the standard ES5 function for retrieving the prototype of an object.
obj.__proto__
is a nonstandard mechanism for retrieving the prototype of an object.- A class is a design pattern consisting of a constructor function and an associated prototype.
Item 31: Prefer Object.getPrototypeOf to __proto__
Things to Remember
- Prefer the standards-compliant Object.getPrototypeOf to the non-standard
__proto__
property. - Implement Object.getPrototypeOf in non-ES5 environments that support
__proto__
.
Item 32: Never Modify __proto__
The special __proto__
property provides an additional power that Object.getPrototypeOf
does not: the ability to modify an object’s prototype link.
Another reason to avoid modifying __proto__
is performance.
Item 33: Make Your Constructors new-Agnostic
Item 34: Store Methods on Prototypes
Things to Remember
- Storing methods on instance objects creates multiple copies of the functions, one per instance object.
- Prefer storing methods on prototypes over storing them on instance objects.
Item 35: Use Closures to Store Private Data
Item 36: Store Instance State Only on Instance Objects
Item 37: Recognize the Implicit Binding of this
Every function has an implicit binding of this
, whose value is determined when the function is called.
Item 38: Call Superclass Constructors from Subclass Constructors
Item 39: Never Reuse Superclass Property Names
Item 40: Avoid Inheriting from Standard Classes
Item 41: Treat Prototypes As an Implementation Detail
Item 42: Avoid Reckless Monkey-Patching
Things to Remember
- Avoid reckless monkey-patching.
- Document any monkey-patching performed by a library.
- Consider making monkey-patching optional by performing the modifications in an exported function.
- Use monkey-patching to provide polyfills for missing standard APIs.
Chapter 5: Arrays and Dictionaries
Item 43: Build Lightweight Dictionaries from Direct Instances of Object
Item 44: Use null Prototypes to Prevent Prototype Pollution
Things to Remember
- In ES5, use
Object.create(null)
to create prototype-free empty objects that are less susceptible to pollution. - In older environments, consider using
{ __proto__: null }
. - But beware that
__proto__
is neither standard nor entirely portable and may be removed in future JavaScript environments. - Never use the name “proto“ as a dictionary key since some environments treat this property specially.
Item 45: Use hasOwnProperty to Protect Against Prototype Pollution
Item 46: Prefer Arrays to Dictionaries for Ordered Collections
Item 47: Never Add Enumerable Properties to Object.prototype
Item 48: Avoid Modifying an Object during Enumeration
Things to Remember
- Make sure not to modify an object while enumerating its properties with a
for...in
loop. - Use a while loop or classic for loop instead of a for…in loop when iterating over an object whose contents might change during the loop.
- For predictable enumeration over a changing data structure, con-sider using a sequential data structure such as an array instead of a dictionary object.
Item 49: Prefer for Loops to for…in Loops for Array Iteration
循环操作优于函数的地方
There is one thing that loops tend to do better than iteration functions: abnormal control flow operations such as break and continue.
For example, it would be awkward to attempt to implement takeWhile using forEach:
1 | function takeWhile(a, pred) { |
We could use an internal exception to implement the early termina-tion of the loop, but this would be awkward and likely inefficient:
1 | function takeWhile(a, pred) { |
Item 50: Prefer Iteration Methods to Loops
Item 51: Reuse Generic Array Methods on Array-Like Objects
Item 52: Prefer Array Literals to the Array Constructor
Chapter 6: Library and API Design
Item 53: Maintain Consistent Conventions
Item 54: Treat undefined As “No Value”
But beware: Truthiness is not always a safe test. If a function should accept the empty string as a legal value, a truthy test will override the empty string and replace it with the default value. Similarly, a function that accepts a number should not use a truthy test if it allows 0 (or NaN, although it’s less common) as an acceptable value.
Item 55: Accept Options Objects for Keyword Arguments
优点
Another benefit of options objects is that any of the arguments can be optional, and a caller can provide any subset of the optional arguments.
以更优雅的方式设计函数
If there are one or two required arguments, it’s better to keep them separate from the options object:
1 | var alert = new Alert(app, message, { |
Things to Remember
- Use options objects to make APIs more readable and memorable.
- The arguments provided by an options object should all be treated as optional.
- Use an extend utility function to abstract out the logic of extracting values from options objects.
Item 56: Avoid Unnecessary State
Things to Remember
- Prefer stateless APIs where possible.
- When providing stateful APIs, document the relevant state that each operation depends on.
Item 57: Use Structural Typing for Flexible Interfaces
Item 58: Distinguish between Array and Array-Like
判断数组
Use ES5’s Array.isArray
to test for true arrays.
In environments that don’t support ES5, you can use the standard Object.prototype.toString
method to test whether an object is an array:
1 | var toString = Object.prototype.toString; |
Item 59: Avoid Excessive Coercion
Item 60: Support Method Chaining
Things to Remember
- Use method chaining to combine stateless operations.
- Support method chaining by designing stateless methods that produce new objects.
- Support method chaining in stateful methods by returning this.
Chapter 7: Concurrency
Item 61: Don’t Block the Event Queue on I/O
The single most important rule of concurrent JavaScript is never to use any blocking I/O APIs in the middle of an application’s event queue.
Things to Remember
- Asynchronous APIs take callbacks to defer processing of expensive operations and avoid blocking the main application.
- JavaScript accepts events concurrently but processes event handlers sequentially using an event queue.
- Never use blocking I/O in an application’s event queue.